From 9caccb8f4814c9583ddba079ebf5c5d1b2a65f3c Mon Sep 17 00:00:00 2001 From: Alastair Tse Date: Thu, 5 Oct 2006 17:29:19 +0100 Subject: [PATCH] [XEND] XendConfig is an extended python dictionary that is used to exchange VM configuration information to allow easy import and export to different file types. Signed-off-by: Alastair Tse --- tools/python/xen/xend/XendConfig.py | 819 ++++++++++++++++++++++++++++ 1 file changed, 819 insertions(+) create mode 100644 tools/python/xen/xend/XendConfig.py diff --git a/tools/python/xen/xend/XendConfig.py b/tools/python/xen/xend/XendConfig.py new file mode 100644 index 0000000000..6506e6abc9 --- /dev/null +++ b/tools/python/xen/xend/XendConfig.py @@ -0,0 +1,819 @@ +#=========================================================================== +# This library is free software; you can redistribute it and/or +# modify it under the terms of version 2.1 of the GNU Lesser General Public +# License as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +#============================================================================ +# Copyright (C) 2006 XenSource Ltd +#============================================================================ + +import re +import time + +from xen.xend import sxp +from xen.xend import uuid +from xen.xend.XendError import VmError +from xen.xend.XendDevices import XendDevices +from xen.xend.XendLogging import log +from xen.xend.PrettyPrint import prettyprintstring + +""" +XendConfig API + + XendConfig will try to mirror as closely the Xen API VM Struct + providing a backwards compatibility mode for SXP dumping, loading. + +XendConfig is a subclass of the python dict in order to emulate the +previous behaviour of the XendDomainInfo.info dictionary. However, +the new dictionary also exposes a set of attributes that implement +the Xen API VM configuration interface. + +Example: + +>>> cfg = XendConfig(cfg = dict_from_xc_domain_getinfo) +>>> cfg.name_label +Domain-0 +>>> cfg['name'] +Domain-0 +>>> cfg.kernel_kernel +/boot/vmlinuz-xen +>>> cfg.kernel_initrd +/root/initrd +>>> cfg.kernel_args +root=/dev/sda1 ro +>>> cfg['image'] +(linux + (kernel /boot/vmlinuz-xen) + (ramdisk /root/initrd) + (root '/dev/sda1 ro')) +>>> + +Internally, XendConfig will make sure changes via the old 'dict' +interface get reflected, if possible, to the attribute store. + +It does this by overriding __setitem__, __getitem__, __hasitem__, +__getattr__, __setattr__, __hasattr__. + +What this means is that as code is moved from the SXP interface to +the Xen API interface, we can spot unported code by tracing calls +to __getitem__ and __setitem__. + +""" + + +LEGACY_CFG_TO_XENAPI_CFG = { + 'uuid': 'uuid', + 'vcpus': 'vcpus_number', + 'maxmem': 'memory_static_max', + 'memory': 'memory_static_min', + 'name': 'name_label', + 'on_poweroff': 'actions_after_shutdown', + 'on_reboot': 'actions_after_reboot', + 'on_crash': 'actions_after_crash', + 'bootloader': 'boot_method', + 'kernel_kernel': 'kernel_kernel', + 'kernel_initrd': 'kernel_initrd', + 'kernel_args': 'kernel_args', + } + +XENAPI_CFG_CUSTOM_TRANSLATE = [ + 'vifs', + 'vbds', + ] + +XENAPI_UNSUPPORTED_IN_LEGACY_CFG = [ + 'name_description', + 'user_version', + 'is_a_template', + 'memory_dynamic_min', + 'memory_dynamic_max', + 'memory_actual', + 'vcpus_policy', + 'vcpus_params', + 'vcpus_features_required', + 'vcpus_features_can_use', + 'vcpus_features_force_on', + 'vcpus_features_force_off', + 'actions_after_suspend', + 'tpm_instance', + 'tpm_backends', + 'bios_boot', + 'platform_std_vga', + 'platform_serial', + 'platform_localtime', + 'platform_clock_offset', + 'platform_enable_audio', + 'builder', + 'grub_cmdline', + 'pci_bus', + 'otherconfig' + ] + +## +## Xend Configuration Parameters +## + + +# All parameters of VMs that may be configured on-the-fly, or at start-up. +VM_CONFIG_ENTRIES = [ + ('autostart', int), + ('autostop', int), + ('name', str), + ('on_crash', str), + ('on_poweroff', str), + ('on_reboot', str), + ('on_xend_stop', str), +] + +# All entries written to the store. This is VM_CONFIG_ENTRIES, plus those +# entries written to the store that cannot be reconfigured on-the-fly. +VM_STORE_ENTRIES = [ + ('uuid', str), + ('vcpus', int), + ('vcpu_avail', int), + ('memory', int), + ('maxmem', int), + ('start_time', float), +] + +VM_STORED_ENTRIES = VM_CONFIG_ENTRIES + VM_STORE_ENTRIES + +# Configuration entries that we expect to round-trip -- be read from the +# config file or xc, written to save-files (i.e. through sxpr), and reused as +# config on restart or restore, all without munging. Some configuration +# entries are munged for backwards compatibility reasons, or because they +# don't come out of xc in the same form as they are specified in the config +# file, so those are handled separately. + +ROUNDTRIPPING_CONFIG_ENTRIES = [ + ('uuid', str), + ('vcpus', int), + ('vcpu_avail', int), + ('cpu_weight', float), + ('memory', int), + ('shadow_memory', int), + ('maxmem', int), + ('bootloader', str), + ('bootloader_args', str), + ('features', str), + ('localtime', int), +] +ROUNDTRIPPING_CONFIG_ENTRIES += VM_CONFIG_ENTRIES + +## Static Configuration + +STATIC_CONFIG_ENTRIES = [ + ('cpu', int), + ('cpus', str), + ('image', list), + ('security', list), # TODO: what if null? +] + +DEPRECATED_ENTRIES = [ + ('restart', str), +] + +## +## Config Choices +## + +CONFIG_RESTART_MODES = ('restart', 'destroy', 'preserve', 'rename-restart') +CONFIG_OLD_DOM_STATES = ('running', 'blocked', 'paused', 'shutdown', + 'crashed', 'dying') + +## +## Defaults +## + +def DEFAULT_VCPUS(info): + if 'max_vcpu_id' in info: return int(info['max_vcpu_id']) + 1 + else: return 1 + +DEFAULT_CONFIGURATION = ( + ('uuid', lambda info: uuid.createString()), + ('name', lambda info: 'Domain-' + info['uuid']), + + ('on_poweroff', lambda info: 'destroy'), + ('on_reboot', lambda info: 'restart'), + ('on_crash', lambda info: 'restart'), + ('features', lambda info: ''), + + + ('memory', lambda info: 0), + ('shadow_memory',lambda info: 0), + ('maxmem', lambda info: 0), + ('bootloader', lambda info: None), + ('bootloader_args', lambda info: None), + ('backend', lambda info: []), + ('device', lambda info: {}), + ('image', lambda info: None), + ('security', lambda info: []), + ('autostart', lambda info: 0), + ('autostop', lambda info: 0), + ('on_xend_stop', lambda info: 'shutdown'), + + ('cpus', lambda info: []), + ('cpu_weight', lambda info: 1.0), + ('vcpus', lambda info: DEFAULT_VCPUS(info)), + ('online_vcpus', lambda info: info['vcpus']), + ('max_vcpu_id', lambda info: info['vcpus']-1), + ('vcpu_avail', lambda info: (1< [0,2,3] + # "0-3,^1,1" -> [0,1,2,3] + try: + if 'cpus' in cfg: + cpus = [] + for c in cfg['cpus'].split(','): + if c.find('-') != -1: + (x, y) = c.split('-') + for i in range(int(x), int(y)+1): + cpus.append(int(i)) + else: + # remove this element from the list + if c[0] == '^': + cpus = [x for x in cpus if x != int(c[1:])] + else: + cpus.append(int(c)) + + cfg['cpus'] = cpus + except ValueError, e: + raise XendConfigError('cpus = %s: %s' % (cfg['cpus'], e)) + + # Parse image SXP outside of image.py + # - used to be only done in image.py + if 'image' in cfg: + cfg['kernel_kernel'] = sxp.child_value(cfg['image'], 'kernel','') + cfg['kernel_initrd'] = sxp.child_value(cfg['image'], 'ramdisk','') + kernel_args = sxp.child_value(cfg['image'], 'args', '') + + # attempt to extract extra arguments from SXP config + arg_ip = sxp.child_value(cfg['image'], 'ip') + if arg_ip: kernel_args += ' ip=%s' % arg_ip + arg_root = sxp.child_value(cfg['image'], 'root') + if arg_root: kernel_args += ' root=%s' % arg_root + + cfg['kernel_args'] = kernel_args + + # TODO: get states + old_state = sxp.child_value(parsed, 'state') + if old_state: + for i in range(len(CONFIG_OLD_DOM_STATES)): + cfg[CONFIG_OLD_DOM_STATES[i]] = (old_state[i] != '-') + + # Xen API extra cfgs + # ------------------ + cfg['vif_refs'] = [] + cfg['vbd_refs'] = [] + for dev_uuid, (dev_type, dev_info) in cfg['device'].items(): + if dev_type == 'vif': + cfg['vif_refs'].append(dev_uuid) + elif dev_type == 'vbd': + cfg['vbd_refs'].append(dev_uuid) + + return cfg + + + def _populate_from_xenapi_vm(self, xenapi_vm): + cfg = {} + + for cfgkey, apikey in LEGACY_CFG_TO_XENAPI_CFG.items(): + try: + cfg[cfgkey] = xenapi_vm[apikey] + except KeyError: + pass + + # Reconstruct image SXP + # TODO: get rid of SXP altogether from here + sxp_image = ['linux'] + if xenapi_vm['kernel_kernel']: + sxp_image.append(['kernel', xenapi_vm['kernel_kernel']]) + if xenapi_vm['kernel_initrd']: + sxp_image.append(['ramdisk', xenapi_vm['kernel_initrd']]) + if xenapi_vm['kernel_args']: + sxp_image.append(['args', xenapi_vm['kernel_args']]) + cfg['image'] = prettyprintstring(sxp_image) + + # make sure device structures are there. + if 'device' not in cfg: + cfg['device'] = {} + if 'vif_refs' not in cfg: + cfg['vif_refs'] = [] + if 'vbd_refs' not in cfg: + cfg['vbd_refs'] = [] + + return cfg + + + def _sync_xen_api_from_legacy_api(self): + """ Sync all the attributes that is supported by the Xen API + from the legacy API configuration. + """ + for cfgkey, apikey in LEGACY_CFG_TO_XENAPI_CFG.items(): + if cfgkey in self: + self.xenapi[apikey] = self[cfgkey] + + def _sync_legacy_api_from_xen_api(self): + for cfgkey, apikey in LEGACY_CFG_TO_XENAPI_CFG.items(): + if apikey in self.xenapi: + self[cfgkey] = self.xenapi[apikey] + + + def _populate_from_xml(self, parsed_xml): + raise NotImplementedError + + def _populate_from_python_config(self, parsed_py): + raise NotImplementedError + + + def get_sxp(self, domain = None, ignore_devices = False, ignore = []): + """ Get SXP representation of this config object. + + Incompat: removed store_mfn, console_mfn + + @keyword domain: (optional) XendDomainInfo to get extra information + from such as domid and running devices. + @type domain: XendDomainInfo + @keyword ignore: (optional) list of 'keys' that we do not want + to export. + @type ignore: list of strings + @rtype: list of list (SXP representation) + """ + sxpr = ['domain'] + + # TODO: domid/dom is the same thing but called differently + # depending if it is from xenstore or sxpr. + + if domain.getDomid() != None: + sxpr.append(['domid', domain.getDomid()]) + + for cfg, typefunc in ROUNDTRIPPING_CONFIG_ENTRIES: + if cfg in self: + if self[cfg] != None: + sxpr.append([cfg, self[cfg]]) + + if 'image' in self: + sxpr.append(['image', self['image']]) + if 'security' in self: + sxpr.append(['security', self['security']]) + if 'shutdown_reason' in self: + sxpr.append(['shutdown_reason', self['shutdown_reason']]) + if 'cpu_time' in self: + sxpr.append(['cpu_time', self['cpu_time']/1e9]) + + sxpr.append(['online_vcpus', self['online_vcpus']]) + + if 'start_time' in self: + uptime = time.time() - self['start_time'] + sxpr.append(['up_time', str(uptime)]) + sxpr.append(['start_time', str(self['start_time'])]) + + sxpr.append(['autostart', self.get('autostart', 0)]) + sxpr.append(['autostop', self.get('autostop', 0)]) + sxpr.append(['on_xend_stop', self.get('on_xend_stop', 'shutdown')]) + + sxpr.append(['status', domain.state]) + + # Marshall devices (running or from configuration) + if not ignore_devices: + for cls in XendDevices.valid_devices(): + found = False + + # figure if there is a device that is running + if domain: + try: + controller = domain.getDeviceController(cls) + configs = controller.configurations() + for config in configs: + sxpr.append(['device', config]) + found = True + except: + log.exception("dumping sxp from device controllers") + pass + + # if we didn't find that device, check the existing config + # for a device in the same class + if not found: + for dev_type, dev_info in self.all_devices_sxpr(): + if dev_type == cls: + sxpr.append(['device', dev_info]) + + return sxpr + + def validate(self): + """ Validate the configuration and fill in missing configuration + with defaults. + """ + + # Fill in default values + for key, default_func in DEFAULT_CONFIGURATION: + if key not in self: + self[key] = default_func(self) + + # Basic sanity checks + if 'image' in self and isinstance(self['image'], str): + self['image'] = sxp.from_string(self['image']) + if 'security' in self and isinstance(self['security'], str): + self['security'] = sxp.from_string(self['security']) + if self['memory'] == 0 and 'mem_kb' in self: + self['memory'] = (self['mem_kb'] + 1023)/1024 + if self['memory'] <= 0: + raise XendConfigError('Invalid memory size: %s' % + str(self['memory'])) + + self['maxmem'] = max(self['memory'], self['maxmem']) + + # Verify devices + for d_uuid, (d_type, d_info) in self['device'].items(): + if d_type not in XendDevices.valid_devices(): + raise XendConfigError('Invalid device (%s)' % d_type) + + # Verify restart modes + for event in ('on_poweroff', 'on_reboot', 'on_crash'): + if self[event] not in CONFIG_RESTART_MODES: + raise XendConfigError('Invalid restart event: %s = %s' % \ + (event, str(self[event]))) + + def device_add(self, dev_type, cfg_sxp = None, cfg_xenapi = None): + if dev_type not in XendDevices.valid_devices(): + raise XendConfigError("XendConfig: %s not a valid device type" % + dev_type) + + if cfg_sxp == None and cfg_xenapi == None: + raise XendConfigError("XendConfig: device_add requires some " + "config.") + + if cfg_sxp: + config = sxp.child0(cfg_sxp) + dev_type = sxp.name(config) + dev_info = {} + + try: + for opt, val in config[1:]: + dev_info[opt] = val + except ValueError: + log.debug('XendConfig.device_add: %s' % config) + pass # SXP has no options for this device + + # create uuid if it doesn't exist + dev_uuid = dev_info.get('uuid', uuid.createString()) + dev_info['uuid'] = dev_uuid + self['device'][dev_uuid] = (dev_type, dev_info) + return dev_uuid + + if cfg_xenapi: + dev_info = {} + if dev_type == 'vif': + if cfg_xenapi.get('MAC'): # don't add if blank + dev_info['mac'] = cfg_xenapi.get('MAC') + # vifname is the name on the guest, not dom0 + # TODO: we don't have the ability to find that out or + # change it from dom0 + #if cfg_xenapi.get('device'): # don't add if blank + # dev_info['vifname'] = cfg_xenapi.get('device') + if cfg_xenapi.get('type'): + dev_info['type'] = cfg_xenapi.get('type') + + dev_uuid = cfg_xenapi.get('uuid', uuid.createString()) + dev_info['uuid'] = dev_uuid + self['device'][dev_uuid] = (dev_type, dev_info) + return dev_uuid + + elif dev_type == 'vbd': + dev_info['uname'] = cfg_xenapi.get('image', None) + dev_info['dev'] = '%s:disk' % cfg_xenapi.get('device') + if cfg_xenapi.get('mode') == 'RW': + dev_info['mode'] = 'w' + else: + dev_info['mode'] = 'r' + + dev_uuid = cfg_xenapi.get('uuid', uuid.createString()) + dev_info['uuid'] = dev_uuid + self['device'][dev_uuid] = (dev_type, dev_info) + return dev_uuid + + + return '' + + def device_sxpr(self, dev_uuid = None, dev_type = None, dev_info = None): + """Get Device SXPR by either giving the device UUID or (type, config). + + @rtype: list of lists + @return: device config sxpr + """ + sxpr = [] + if dev_uuid != None and dev_uuid in self['device']: + dev_type, dev_info = self['device'] + + if dev_type == None or dev_info == None: + raise XendConfigError("Required either UUID or device type and " + "configuration dictionary.") + + sxpr.append(dev_type) + config = [(opt, val) for opt, val in dev_info.items() \ + if opt != 'type'] + sxpr += config + + return sxpr + + def all_devices_sxpr(self): + sxprs = [] + for dev_type, dev_info in self['device'].values(): + sxpr = self.device_sxpr(dev_type = dev_type, dev_info = dev_info) + sxprs.append((dev_type, sxpr)) + return sxprs + + +# +# debugging +# + +if __name__ == "__main__": + pass + -- 2.30.2